﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace luatags
{
    class Program
    {
        class TagInfo
        {
            public string name;
            public string filename;
            public string cmd;
            public string kind;
            public byte[] bytes;
        }

        static List<TagInfo> m_tags;
        static string m_path;

        static void AddTag(string name, string fname, string line, string kind)
        {
            line = @"/^" + line.Replace(@"/", @"\/" ) + "$/;\"";

            TagInfo tag = new TagInfo();
            tag.name = name;
            tag.filename = fname;
            tag.cmd = line;
            tag.kind = kind;
            tag.bytes = Encoding.UTF8.GetBytes(name);
            m_tags.Add(tag);
        }

        static void SaveFile(string fname)
        {
            m_tags.Sort(ComparerTags);
            List<string> lines = new List<string>();
            foreach (TagInfo tag in m_tags)
            {
                string line = string.Format("{0}\t{1}\t{2}\t{3}",
                    tag.name,
                    tag.filename,
                    tag.cmd,
                    tag.kind);
                lines.Add(line);
            }

            // file header
            lines.Insert(0, "!_TAG_FILE_FORMAT	2	/extended format; --format=1 will not append ;\" to lines/");
            lines.Insert(1, "!_TAG_FILE_SORTED	1	/0=unsorted, 1=sorted, 2=foldcase/");
            lines.Insert(2, "!_TAG_PROGRAM_AUTHOR	Hproof	/hproof@qq.com/");
            lines.Insert(3, "!_TAG_PROGRAM_NAME	Lua Ctags	//");
            lines.Insert(4, "!_TAG_PROGRAM_VERSION	1.0	//");
            
            File.WriteAllLines(fname, lines.ToArray(), Encoding.UTF8);
        }
        static int ComparerTags(TagInfo a, TagInfo b )
        {
            byte[] b1 = a.bytes;
            byte[] b2 = b.bytes;
            int n = Math.Min(a.bytes.Length, b.bytes.Length);
            for (int i = 0; i < n; i++)
            {
                if (b1[i] < b2[i]) return -1;
                if (b1[i] > b2[i]) return 1;
            }
            if (b1.Length < b2.Length) return -1;
            if (b1.Length > b2.Length) return 1;
            return 0;
        }

        static int Main(string[] args)
        {
            //string path = @"E:\svnproj\web_mmo\server-lua\script";
            string path = Directory.GetCurrentDirectory();
            string fname = Path.Combine(path, @"tags");

            m_tags = new List<TagInfo>();
            m_path = path;
            DoPath(path);
            SaveFile(fname);
            return 0;
        }

        static void DoPath( string path )
        {
            string[] files = Directory.GetFiles(path, "*.*");
            foreach (string file in files)
            {
                DoFile(file);
            }

            string[] dirs = Directory.GetDirectories(path);
            foreach (string dir in dirs)
            {
                DoPath(dir);
            }
        }

        static void DoFile(string fname)
        {
            string ext = Path.GetExtension(fname).ToLower();
            DoFileHandler handler = null;
            if( lang_map.TryGetValue(ext, out handler) )
            {
                string[] lines = File.ReadAllLines(fname);
                fname = "." + fname.Substring(m_path.Length);
                handler(fname, lines, m_tags);
            }
        }

        delegate void DoFileHandler(string fname, string[] lines, List<TagInfo> list );
        static Dictionary<string, DoFileHandler> lang_map = new Dictionary<string,DoFileHandler>()
        {
            {".lua", do_lua},
        };

        // do lua
        static void do_lua(string fname, string[] lines, List<TagInfo> list)
        {
            string last_table = null;
            foreach (string line in lines)
            {
                TagInfo tag = new TagInfo();
                Match m;

                // check last_table
                if (last_table != null)
                {
                    if (Regex.IsMatch(line, @"^\s*}$"))
                    {
                        last_table = null;
                    }
                }

                // function:
                //      function foo.woo:boo
                if (line.Contains("function"))
                {
                    m = Regex.Match(line, @"^\s*function\s+([\w\.\:]+)\s*\(");
                    if (m.Success)
                    {
                        AddTag(m.Groups[1].Value, fname, line, "f");
                        last_table = null;
                        continue;
                    }
                }

                // label:
                //      ^foo = $
                //      ^foo = -- comment $
                //      ^foo = {
                //      ^foo = foo or {
                m = Regex.Match(line, @"^(local\s+)?(\w*)\s*=");
                if( m.Success )
                {
                    string name = m.Groups[2].Value;
                    string line2 = line.Substring(m.Length);
                    if( Regex.IsMatch(line2, @"^\s*$") ||
                        Regex.IsMatch(line2, @"^\s*--.*$" ) ||
                        Regex.IsMatch(line2, @"^\s*{") ||
                        Regex.IsMatch(line2, @"^\s*" + name + @"\s+or\s+{" ) )
                    {
                        AddTag(name, fname, line, "d" );
                        last_table = name;
                        continue;
                    }
                }

                // member:
                //      \sfoo = xxxx,           -- last_table!
                //      ^foo.woo = 
                if( last_table != null )
                {
                    m = Regex.Match(line, @"^\s+(\w+)\s*=[^,]*,");
                    if (m.Success)
                    {
                        AddTag(last_table + "." + m.Groups[1].Value, fname, line, "M");
                        //last_table = null;
                        continue;
                    }
                }
                m = Regex.Match(line, @"^(\w+[\w\.]+\w+)\s*=");
                if (m.Success)
                {
                    AddTag(m.Groups[1].Value, fname, line, "m");
                    last_table = null;
                    continue;
                }
            }
        }
    }
}
